import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.sound.sampled.DataLine.Info;

public class AudioFile {
	private InputStream waveStream = null;
    private AudioFormat audioFormat = null;
    private	AudioInputStream audioInputStream = null;
    
    private int frameSize;		// In bytes
    private float frameRate;	// In seconds
	private long currentPosition = 0;
	private float videoFrameIntervalDuration = 1f/24f;        // In seconds
	
	public AudioFile(String fileName){
		// opens the inputStream
    	try {
    		this.waveStream = new FileInputStream(fileName);
    	} catch (FileNotFoundException e) {
    	    e.printStackTrace();
    	    return;
    	}

		try {
		    audioInputStream = AudioSystem.getAudioInputStream(this.waveStream);
		} catch (UnsupportedAudioFileException e1) {
		    e1.printStackTrace();
		    return;
		} catch (IOException e1) {
		    e1.printStackTrace();
		    return;
		}
		
		// Obtain the information about the AudioInputStream
		audioFormat = audioInputStream.getFormat();
		frameSize = audioFormat.getFrameSize();
		frameRate = audioFormat.getFrameRate();
	}
	
	public AudioFormat getAudioFormat(){
		return audioFormat;
	}
	
	public AudioFileFormat.Type getAudioType() throws UnsupportedAudioFileException, IOException{
		return AudioSystem.getAudioFileFormat(waveStream).getType();
	}
	
	public void setVideoFrameIntervalDuration(float seconds){
		// Duration is in second
		videoFrameIntervalDuration = seconds;
	}
	
	public void playSound(byte[] data){
		if(data == null) return;

		Info info = new Info(SourceDataLine.class, audioFormat);

		// opens the audio channel
		SourceDataLine dataLine = null;
		try {
		    dataLine = (SourceDataLine) AudioSystem.getLine(info);
		    dataLine.open(audioFormat, data.length);
		} catch (LineUnavailableException e1) {
		    e1.printStackTrace();
		}

		// Starts the music :P
		dataLine.start();
		try {
		    dataLine.write(data, 0, data.length);
		} finally {
		    // plays what's left and and closes the audioChannel
		    dataLine.drain();
		    dataLine.close();
		}
	}
	
	public boolean setPositionBasedOnSound(long position) {
		// position is in bytes
		long finishPos = (audioInputStream.getFrameLength()-1) * frameSize;
		if(position < 0 || position > finishPos)
			return false;
		
		long offset = position - currentPosition;
		if(offset < 0) 
			return false;

		try {
			audioInputStream.skip(offset);
			currentPosition = position;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}
	
	public boolean setPositionBasedOnVideo(int videoFrameIndex){
		float timeStamp = videoFrameIndex * videoFrameIntervalDuration;
		// in bytes
		long position = ((int) (frameRate * timeStamp)) * frameSize;		
		return setPositionBasedOnSound(position);
	}
	
	public byte[] readDurationSoundData(int videoFrameDurationLength) throws Exception{
		
		int intervals = videoFrameDurationLength;
		if(intervals <=0 )
			return null;
		if(videoFrameIntervalDuration == 0) 
			throw new Exception("Uninitialized videoFrameIntervalDuration");
		
		// in seconds
		double timeDuration = intervals * videoFrameIntervalDuration;
		
		// in bytes
		long byteNum = ((int) (frameRate * timeDuration)) * frameSize;
		long finishPos = audioInputStream.getFrameLength() * frameSize;
		if(currentPosition + byteNum > finishPos){
			//return null;
			byteNum = finishPos - currentPosition;
			if(byteNum <=0 ) 
				return null;
		}

		// read sound data in to buffer
		byte[] src = new byte[(int) (byteNum)];
		readSoundData(src);
		return src;
	}
	
	public byte[] readDurationSoundData(float timeDuration) throws Exception{
		if(timeDuration <=0 )
			return null;
		if(videoFrameIntervalDuration == 0) 
			throw new Exception("Uninitialized videoFrameIntervalDuration");
		
		// in bytes
		long byteNum = ((int) (frameRate * timeDuration)) * frameSize;
		long finishPos = audioInputStream.getFrameLength() * frameSize;
		if(currentPosition + byteNum > finishPos){
			byteNum = finishPos - currentPosition;
			if(byteNum <=0 ) 
				return null;
		}
		
		// read sound data in to buffer
		byte[] data = new byte[(int) byteNum];
		this.readSoundData(data);
		return data;
	}
	
    public boolean readSoundData(byte[] buffer){

		try {
		    //Read raw data
		    int offset = 0;
	        int numRead = 0;
	        int length = buffer.length;
	        while (offset < length && (numRead =
	        	audioInputStream.read(buffer, offset, length-offset)) >= 0) {
	        	if(numRead == -1){
	        		currentPosition += offset;
	        		return false;
	        	}
	            offset += numRead;
	        }
	        currentPosition += length;
		} catch (IOException e1) {
		   e1.printStackTrace();
		   return false;
		} 
    	return true;
    }
	
	public void printInfo(){
		System.out.println("FrameLength:"+audioInputStream.getFrameLength());
		System.out.println("audioFormat:"+this.audioFormat);
	}
	
	public long getCurrentPosition(){
		return currentPosition;
	}
	
}
